/*
 * ClientInstaller.java
 *
 * $Id: ClientInstaller.java,v 1.6 2009-09-17 12:10:05 mario Exp $
 */
package org.ceteca.explica.client.installer;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.rmi.AccessException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.ceteca.explica.client.AutoUpdateInitProgressBarOperation;
import org.ceteca.explica.client.ClientContext;
import org.ceteca.explica.client.UpdateInitProgressBarOperation;
import org.ceteca.explica.core.InterfaceErrors;
import org.ceteca.explica.core.InterfaceExplica;
import org.ceteca.explica.core.installer.EntityOT;
import org.ceteca.explica.core.installer.Installer;
import org.ceteca.explica.core.installer.ModuleLauncher;
import org.ceteca.explica.core.installer.ResourceException;
import org.ceteca.explica.core.installer.ResourceOT;
import org.ceteca.javaglade.toolkit.swt.StatusBar;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;

/**
* This class implements the client manager of the installing process.
* It requests the resources list to the server of the application,
* writes the resources to the local path and updates the local catalog 
* of installed resources, when required.
* <br/>
* Responsibilities:
* <ul>
*  <li>Communication with the remote Installer server object.</li>
*  <li>Install in the client machine the new or updated resources.</li>
*  <li>Update the client catalog of installed resources.</li>
*  <li>Configure the client application for using the new resources.</li>
* </ul>
* <br/>
* @author Mario García García <mario@imagos.es>
 * Copyright (c) 2008 Fundación Centro Tecnolóxico da Carne
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * <br/>
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * <br/>
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
public class ClientInstaller {
	/**
	 * Singleton pattern. Unique instance of the ClientInstaller class.
	 */
	private static ClientInstaller instance;
	
	private Hashtable<String, ModuleLauncher> modules;
	
	private ClientInstaller() {
		// Init the catalog of modules
		modules = new Hashtable<String, ModuleLauncher>();
	}
	
	/**
	 * Gets an instance of the ClientInstaller class.
	 * @return ClientInstaller, the unique instance of the ClientInstaller class 
	 * (Singleton pattern).
	 */
	public static ClientInstaller getInstance() {
		if (instance == null)
			instance = new ClientInstaller();
		
		return instance;
	}
	
	public Hashtable<String, ModuleLauncher> getModules() {
		return modules;
	}
	public void setModules(Hashtable<String, ModuleLauncher> modules) {
		this.modules = modules;
	}
	
	/**
	 * (Re)Installs all the resources of the list in the client application.
	 * @param lResToReinstall ArrayList, list of resources to (re)install.
	 * @param ins Installer, instance of the remote Installer object.
	 * @return int, the number of (re)installed resources.
	 */
	private int reinstallResources(ArrayList lResToReinstall, Installer ins) {
		int numInstalledRes = 0;
		// Re-install the modules that have been updated
		try {
			for (int i=0; lResToReinstall != null && i<lResToReinstall.size(); i++) {
				// Get the resource data (as a byte array)
				ResourceOT res = (ResourceOT)lResToReinstall.get(i);
				byte[] lBytes = ins.getResource(res.getCodResource());
				
				// Write the bytes to the local resources route
				try {
					this.installResource(res, lBytes);
					numInstalledRes++;
					ClientContext.getInstance().getLogger().
						debug("Resource successfully (re)installed: " + res.getCodResource() + " -- " + res.getDescription());
				} // try
				catch (ResourceException resEx) {
					ClientContext.getInstance().getLogger().
						error("Error in resource (re)installation!", resEx);
				} // catch
			} // for each resource to reinstall
		} // try
		catch (Exception ex) {
			ClientContext.getInstance().getLogger().
				error("General error in resources (re)installation!", ex);
		} // catch
		
		return numInstalledRes;
	}
	
	/**
	 * Configures and creates module launchers for all the modules
	 * installed in the client application.
	 * @param lRes List, list of available resources.
	 */
	private void configInstalledModules(List lRes) {
		if (lRes != null && !lRes.isEmpty()) {
			// Get an iterator for the installed resources list
			Iterator it = lRes.iterator();
			while (it != null && it.hasNext()) {
				// Get the next resource
				ResourceOT res = (ResourceOT)it.next();
				if (res != null && res.getCodResource() != null) {
					// If the resource is a module (the path of the resource must end with the .JAR extension)
			        if (res.getPath().toUpperCase().endsWith(".JAR")) {
			        	ClientContext.getInstance().getLogger().
							debug("Installing module: "  + res.getCodResource() + " - " + res.getPath() + " ...");
			        	InputStream is = null;
			        	try {
				        	File f = null;
				        	URL url = this.getClass().getResource(getAbsolutePath(res.getPath()));
							if (url != null) {
								System.out.println("CHECK FILE PATH = " + url.getPath());
								f = new File(url.getPath());
							}
							else {
								f = new File(res.getPath());
							}
				        	
				        	// Search the module configuration file inside the JAR file.
				        	ZipFile jar = new JarFile(f);
				        	ZipEntry entry = jar.getEntry(InterfaceExplica.EXPLICA_MODULE_CONFIG_FILENAME);
				        	
				        	ClientContext.getInstance().getLogger().
								debug("%%%%%%%%%%%%%%%%% Module JAR entries %%%%%%%%%%%%%%%%%%%%%");
				        	Enumeration e = jar.entries();
				        	while (e.hasMoreElements()) {
				        		ClientContext.getInstance().getLogger().
									debug(e.nextElement());
				        	} // for each JAR entry
				        	
				        	ClientContext.getInstance().getLogger().
								debug("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
				        	
				        	is = jar.getInputStream(entry);
			        	} // try
			        	catch (Exception ex) {
			        		ClientContext.getInstance().getLogger().
								error("Error in module configuration!", ex);
			        	} // catch
			        	
			        	// Create a module launcher for the module
			        	ModuleLauncher moduleLauncher = null;
			        	try {
			        		if (is != null) {
			        			ClientContext.getInstance().getLogger().
			        				debug("CATALOGO DE MENSAJES: " + ClientContext.getInstance().getMsgCatalog());
			        			
			        			moduleLauncher = new ModuleLauncher(is);
			        			is.close();
			        			ClientContext.getInstance().getLogger().
									debug("Module launcher succesfully initialized for the module: " + res.getCodResource() + " - " + res.getPath());
			        			
			        			ClientContext.getInstance().addModuleEntities(moduleLauncher.getModuleData());
			        			ClientContext.getInstance().getLogger().
									debug(String.format("Catalog of available entities successfully updated with the entities of the module %s (%s)!", res.getCodResource(), res.getPath()));
			        			
			        			ClientContext.getInstance().getLogger().debug("Module entities: " + moduleLauncher.getModuleData().getName());
			        			for (EntityOT ent : moduleLauncher.getModuleData().getLEntities()) {
			        				ClientContext.getInstance().getLogger().debug("\t" + ent.getKey());
			        			}
			        		} // if module config file opened
			        		else {
			        			ClientContext.getInstance().getLogger().
									error("Error in module configuration! Couldn't find the module configuration file (moduleConfig.xml) for the module: " + res.getCodResource() + " - " + res.getPath());
			        		} // else no module config file found
			        	} // try
			        	catch (Exception ex) {
			        		ex.printStackTrace();
			        	} // catch
			        	
			        	// Add module launcher to the client's module catalog
			        	if (moduleLauncher != null) {
			        		this.modules.put(res.getCodResource(), moduleLauncher);
			        		ClientContext.getInstance().getLogger().
								debug("New module added to the catalog: " + res.getCodResource() + " - " + moduleLauncher);				        	
			        		ClientContext.getInstance().getLogger().
								debug("Total modules in the catalog: " + this.modules.size());
			        	} // if moduleLauncher != null
			        } // if resource is module (resource.getPath ends with .JAR extension)
				} // if res is not null
			} // for each resource
		} // if lRes not empty
	}
	
	/**
	 * Searches for new or updated modules and install them in the local
	 * client of the application. 
	 */
	public void configClient() {
		// List the system environment properties
		Properties lProp = System.getProperties();
		Iterator itProp = lProp.keySet().iterator();
		ClientContext.getInstance().getLogger().
			debug("------------ List of environment properties ------------");
		while (itProp != null && itProp.hasNext()) {
			Object key = itProp.next();
			ClientContext.getInstance().getLogger().
				debug(key + " --> " + lProp.get(key));
		}
		ClientContext.getInstance().getLogger().
			debug("--------------------------------------------------------");
		
		// Get the list of resources and modules to install in the client application
		List lRes = null;
		try {
			// El servidor de registro de RMI devuelve un objeto remoto genérico
			// que debe ser convertido al tipo correspondiente. Ojo: El nombre por el
			// que buscamos debe coincidir con el que se le dio al servicio en el
			// momento en que se registró (AlmacenImpl.java)
			Registry registry = LocateRegistry.getRegistry(InterfaceExplica.RMI_REGISTRY_PORT);
			ClientContext.getInstance().getLogger().
				debug("RMI registry located!");
			Installer ins = (Installer)registry.lookup(InterfaceExplica.JNDI_INSTALLER_EXPORT_NAME);
			ClientContext.getInstance().getLogger().
				debug("RMI INSTALLER service located!");
			ArrayList lResToReinstall = new ArrayList();

			// Check the version of all the installed modules
			try {
				// Get the list of available resources in the application server
				lRes = ins.getResourcesList();
				ClientContext.getInstance().getLogger().
					debug("################## List of available resources ################");
				if (lRes != null && !lRes.isEmpty()) {
					Iterator it = lRes.iterator();
					// for each available resource
					while (it != null && it.hasNext()) {
						ResourceOT res = (ResourceOT)it.next();
						ClientContext.getInstance().getLogger().
							debug("Available resource: " + res.getCodResource() + " (" + res.getPath() + ")");
						
						if (res != null && res.getCodResource() != null) {
							// Get the installed version of the resource
							Integer currentVersion = this.getInstalledVersion(res.getCodResource());
							ClientContext.getInstance().getLogger().
								debug("Installed client version of the resource: " + (currentVersion.intValue()==-1?"NOT INSTALLED YET":"VERSION "+currentVersion.intValue()));
							
							// Check if the resource is up to date
							if (!ins.isUpdated(res.getCodResource(), currentVersion)) {
								// If the installed version is different from the latest then
								// add the resource to the list of resources to reinstall
								ClientContext.getInstance().getLogger().
									debug("Resource: " + res.getCodResource() + " -- " + res.getDescription() + " added to the (re)installing resources list!");
								lResToReinstall.add(res);
							} // if res is not up-to-date
						} // if res is not null
					} // for each available resource
				} // if lRes not empty
				ClientContext.getInstance().getLogger().
					debug("################################################################");
			}
			catch (Exception ex) {
				ClientContext.getInstance().getLogger().
					error("Error in resources checking!", ex);
			}
			
			// (Re)Install all the non up-to-date resources
			int numInstalledRes = this.reinstallResources(lResToReinstall, ins);
			
			// Update the resources local file to keep the latest versions of
			// the installed modules/resources
			try {
				if (numInstalledRes > 0) {
					this.updateLocalResourceCatalog(lRes);
					ClientContext.getInstance().getLogger().
						info("Client catalog of installed resources successfully updated!");
				}
			}
			catch (Exception ex) {
				ClientContext.getInstance().getLogger().
					debug("Error in client's resource catalog updating!", ex);
			}

			// Configure and create module launchers for the available modules.
			this.configInstalledModules(lRes);
		}
		catch (AccessException aEx) {
			ClientContext.getInstance().getLogger().
				error("Error in client resources (re)installation! Access forbidden to the remote Installer object. Your system has not the needed permissions for accessing the remote operation!", aEx);
		}
		catch (NotBoundException nEx) {
			ClientContext.getInstance().getLogger().
				error("Error in client resources (re)installation! There is no remote Installer object bounded to the provided JNDI object name!", nEx);
		}
		catch (RemoteException rEx) {
			ClientContext.getInstance().getLogger().
				error("Error in client resources (re)installation! The remote Installer object couldn't be found or an error took place during the remote method invocation!", rEx);
		}
	}

	/**
	 * Searches for new or updated modules and install them in the local
	 * client of the application. 
	 */
	public void configClient(Display display, ProgressBar progressBar, StatusBar statusBar, Shell initWindow) {
		// List the system environment properties
		Properties lProp = System.getProperties();
		Iterator itProp = lProp.keySet().iterator();
		ClientContext.getInstance().getLogger().
			debug("------------ List of environment properties ------------");
		while (itProp != null && itProp.hasNext()) {
			Object key = itProp.next();
			ClientContext.getInstance().getLogger().
				debug(key + " --> " + lProp.get(key));
		}
		ClientContext.getInstance().getLogger().
			debug("--------------------------------------------------------");
		this.updateProgress(display, progressBar, 1, statusBar, "Got environment properties");
		
		AutoUpdateInitProgressBarOperation autoProgress = new AutoUpdateInitProgressBarOperation(display, progressBar, statusBar, "Waiting for server connection ...");
		// Create the thread supplying it with the runnable object
	    Thread thread = new Thread(autoProgress);
	    // Start the thread
	    thread.start();
		
		// Get the list of resources and modules to install in the client application
		List lRes = null;
		try {
			// El servidor de registro de RMI devuelve un objeto remoto genérico
			// que debe ser convertido al tipo correspondiente. Ojo: El nombre por el
			// que buscamos debe coincidir con el que se le dio al servicio en el
			// momento en que se registró (AlmacenImpl.java)
			Registry registry = LocateRegistry.getRegistry(InterfaceExplica.RMI_REGISTRY_PORT);
			ClientContext.getInstance().getLogger().
				debug("RMI registry located!");
			Installer ins = (Installer)registry.lookup(InterfaceExplica.JNDI_INSTALLER_EXPORT_NAME);
			ClientContext.getInstance().getLogger().
				debug("RMI INSTALLER service located!");
			ArrayList lResToReinstall = new ArrayList();

			// Check the version of all the installed modules
			try {
				// Get the list of available resources in the application server
				lRes = ins.getResourcesList();
				ClientContext.getInstance().getLogger().
					debug("################## List of available resources ################");
		
				// Stop the automatic progress update thread because the server has already responded
				autoProgress.stop();
				
				this.updateProgress(display, progressBar, 1, statusBar, "Got server connection");
				
				if (lRes != null && !lRes.isEmpty()) {
					Iterator it = lRes.iterator();
					// for each available resource
					while (it != null && it.hasNext()) {
						ResourceOT res = (ResourceOT)it.next();
						ClientContext.getInstance().getLogger().
							debug("Available resource: " + res.getCodResource() + " (" + res.getPath() + ")");
						
						if (res != null && res.getCodResource() != null) {
							// Get the installed version of the resource
							Integer currentVersion = this.getInstalledVersion(res.getCodResource());
							ClientContext.getInstance().getLogger().
								debug("Installed client version of the resource: " + (currentVersion.intValue()==-1?"NOT INSTALLED YET":"VERSION "+currentVersion.intValue()));
							
							// Check if the resource is up to date
							if (!ins.isUpdated(res.getCodResource(), currentVersion)) {
								// If the installed version is different from the latest then
								// add the resource to the list of resources to reinstall
								ClientContext.getInstance().getLogger().
									debug("Resource: " + res.getCodResource() + " -- " + res.getDescription() + " added to the (re)installing resources list!");
								lResToReinstall.add(res);
							} // if res is not up-to-date
						} // if res is not null
					} // for each available resource
				} // if lRes not empty
				ClientContext.getInstance().getLogger().
					debug("################################################################");
			}
			catch (Exception ex) {
				ClientContext.getInstance().getLogger().
					error("Error in resources checking!", ex);
			}
			
			// (Re)Install all the non up-to-date resources
			int numInstalledRes = this.reinstallResources(lResToReinstall, ins);
			
			this.updateProgress(display, progressBar, 1, statusBar, String.format("(Re)Installing Explica resources (%d)", numInstalledRes));
			
			// Update the resources local file to keep the latest versions of
			// the installed modules/resources
			try {
				if (numInstalledRes > 0) {
					this.updateLocalResourceCatalog(lRes);
					ClientContext.getInstance().getLogger().
						info("Client catalog of installed resources successfully updated!");
					
					this.updateProgress(display, progressBar, 1, statusBar, "Updated Explica resources catalog");
				}
			}
			catch (Exception ex) {
				ClientContext.getInstance().getLogger().
					debug("Error in client's resource catalog updating!", ex);
			}

			// Configure and create module launchers for the available modules.
			this.configInstalledModules(lRes);
			this.updateProgress(display, progressBar, 1, statusBar, "Explica modules installed");
		}
		catch (AccessException aEx) {
			ClientContext.getInstance().getLogger().
				error("Error in client resources (re)installation! Access forbidden to the remote Installer object. Your system has not the needed permissions for accessing the remote operation!", aEx);
		}
		catch (NotBoundException nEx) {
			ClientContext.getInstance().getLogger().
				error("Error in client resources (re)installation! There is no remote Installer object bounded to the provided JNDI object name!", nEx);
		}
		catch (RemoteException rEx) {
			ClientContext.getInstance().getLogger().
				error("Error in client resources (re)installation! The remote Installer object couldn't be found or an error took place during the remote method invocation!", rEx);
		}
		
		this.updateProgress(display, progressBar, 1, statusBar, "Explica configuration completed");
	}
	
	/**
	 * Gets the version number of a client resource. If the resource has not 
	 * been installed in the client application, i.e. is not available in the client,
	 * this method returns -1 as version number.
	 * @param codResource String, code of the searched resource.
	 * @return Integer, version number of the client resource; -1 if the resource is
	 * not available in the client.
	 * @throws ResourceException, in case any error takes place during the 
	 * version checking. 
	 */
	private Integer getInstalledVersion(String codResource) throws ResourceException {
		Integer version = null;
		
		// Get the client's properties file with the catalog of intalled resources.
		File f = null;
		URL url = this.getClass().getResource(getAbsolutePath(InterfaceExplica.INSTALLER_LOCAL_RESOURCES_PROPERTIES_FILE));
		if (url != null) {
			System.out.println("CHECK FILE PATH = " + url.getPath());
			f = new File(url.getPath());
		}
		else {
			f = new File(InterfaceExplica.INSTALLER_LOCAL_RESOURCES_PROPERTIES_FILE);
		}
		Properties prop = new Properties();
		try {
			prop.load(new FileInputStream(f));
		}
		catch (Exception ex) {
			throw new ResourceException(InterfaceErrors.ERR_RESOURCE_NOT_FOUND, ex);
		}
		
		try {
			// Log the catalog of resources available in the client
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			PrintStream buf = new PrintStream(bos);
			prop.list(buf);
			buf.flush();
			ClientContext.getInstance().getLogger().
				debug("-------------- LOCAL RESOURCES CATALOG -------------------\n" +
					 bos.toString() + 
					 "\n-------------------------------------------------------");
			buf.close();
			// Get the installed version of the searched resource in the client
			String s = (String)prop.get(codResource);
			// if version number is null or is empty then return resource is not
			// locally installed (version = -1)
			if (s == null || s.length() <= 0)
				return new Integer(-1);
			else {
				ClientContext.getInstance().getLogger().
					debug("RESOURCE LOCALLY FOUND: [" + codResource + ", " + s + "]");
				version = new Integer((String)prop.get(codResource));
			} // else version number found
		}
		catch (Exception ex) {
			throw new ResourceException(codResource, InterfaceErrors.ERR_RESOURCE_INCORRECT_VERSION, ex);
		}
		
		return version;
	}
	
	/**
	 * Installs a resource in the client application. Writes the resource
	 * content loaded from the server to a local file in the correct path, 
	 * so the resource will be available for the execution.
	 * @param res ResourceOT, information of the resource.
	 * @param resData byte[], content of the resource file.
	 * @throws ResourceException, in case any error takes place during the
	 * resource installation.
	 */
	private void installResource(ResourceOT res, byte[] resData) throws ResourceException {
		ClientContext.getInstance().getLogger().
			debug("Installing resource: " + res.getCodResource() + "\n" + resData);

		File f = new File(res.getPath());
		
		// Write the new version of the resource to the local resources' directory
	    try {
	    	// If the file doesn't exist (first time the resource is installed)
	    	// then create the new file to store the resource
	    	if (!f.exists()) {
	    		// Before creating the new file for the resource the parent directory
	    		// hierarchy must be created (in case it doesn't already exist)
	    		String path = f.getPath();
	    		ClientContext.getInstance().getLogger().
					debug("Path where the resource should be installed: " + path);
	    		
	    		String parentDirs = path.substring(0, path.lastIndexOf("/"));
	    		File dirs = new File(parentDirs);
	    		dirs.mkdirs();
	    		
	    		// Create the new file the resource will be stored into
	    		f.createNewFile();
	    	} // if resource's file doesn't exist
	    	
	    	// Write the bytes (resource's data) to the file
	        FileOutputStream fos = new FileOutputStream(f);
	        fos.write(resData, 0, resData.length);
	        fos.close();
	    } 
	    catch (IOException e) {
	    	throw new ResourceException(res.getCodResource(), res.getPath(), InterfaceErrors.ERR_RESOURCE_NOT_WRITTEN, e);
	    }
	}
	
	/**
	 * Updates the client catalog of installed resources with the
	 * new versions of the available resources. 
	 * @param lRes List, list of installed (available) resources.
	 * @throws ResourceException, in case any error takes place during
	 * the client catalog updating.
	 */
	private void updateLocalResourceCatalog(List lRes) throws ResourceException {
		// Open the client catalog of installed resources
		File f = new File(InterfaceExplica.INSTALLER_LOCAL_RESOURCES_PROPERTIES_FILE);
		Properties prop = new Properties();
		try {
			prop.load(new FileInputStream(f));
		}
		catch (Exception ex) {
			throw new ResourceException(InterfaceErrors.ERR_RESOURCE_NOT_FOUND, ex);
		}
	
		ClientContext.getInstance().getLogger().
			debug("LOCAL CATALOG: " + f.getPath() + " " + f.exists());
		
		if (f != null && f.exists() && lRes != null && !lRes.isEmpty()) {
			try {
				Iterator it = lRes.iterator();
				while (it != null && it.hasNext()) {
					ResourceOT res = (ResourceOT)it.next();
					if (res != null && res.getCodResource() != null) {
						ClientContext.getInstance().getLogger().
							debug("Client catalog updated with the resource: " + res.getCodResource() + " -- " + res.getDescription() + ": " + res.getVersion().toString());
						prop.put(res.getCodResource(), res.getVersion().toString());
					} // if res is not null
				} // for each resource
				
				prop.store(new FileOutputStream(f), "List of installed resources and their versions");
				
				ClientContext.getInstance().getLogger().
					debug("Client catalog successfully updated!");
			}
			catch (Exception ex) {
				throw new ResourceException(InterfaceErrors.ERR_RESOURCE_UPDATE_CATALOG, ex);
			}
		} // if lRes not empty
	}
	
	public void updateProgress(Display display, ProgressBar progressBar, int progressInc, StatusBar statusBar, String statusText) {
		if (progressInc <= 0)
			display.asyncExec(new UpdateInitProgressBarOperation(progressBar, statusBar, statusText));
		else
			display.asyncExec(new UpdateInitProgressBarOperation(progressBar, progressInc, statusBar, statusText));
		
/*		try {
			Thread.sleep(1000);
		} catch (Throwable th) {
		}
*/
	}
	
	public static String getAbsolutePath(String path) {
		if (path != null && path.length() > 0) {
			if (!path.startsWith(System.getProperty(InterfaceExplica.FILE_SEPARATOR_PROPERTYNAME)))
				return System.getProperty(InterfaceExplica.FILE_SEPARATOR_PROPERTYNAME) + path;
			else
				return path;
		}
		else
			return null;
	}
	
	/**
	 * Returns tag Id assigned to CVS source file.
	 */
	public static String getRevision() {
		return "$Id: ClientInstaller.java,v 1.6 2009-09-17 12:10:05 mario Exp $";
	}
}
